/************************************************************************
* ORGANIZATION : SavageHomeAutomation
* PROJECT : Access Control using Pi4J & Raspberry Pi
* FILENAME : AccessControl.java
* **********************************************************************
*
* Copyright (C) 2013 Robert Savage
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.pi4j.component.light.LED;
import com.pi4j.component.light.impl.GpioLEDComponent;
import com.pi4j.component.relay.Relay;
import com.pi4j.component.relay.RelayListener;
import com.pi4j.component.relay.RelayState;
import com.pi4j.component.relay.RelayStateChangeEvent;
import com.pi4j.component.relay.impl.GpioRelayComponent;
import com.pi4j.component.sensor.Sensor;
import com.pi4j.component.sensor.SensorListener;
import com.pi4j.component.sensor.SensorState;
import com.pi4j.component.sensor.SensorStateChangeEvent;
import com.pi4j.component.sensor.impl.GpioSensorComponent;
import com.pi4j.component.switches.MomentarySwitch;
import com.pi4j.component.switches.SwitchListener;
import com.pi4j.component.switches.SwitchState;
import com.pi4j.component.switches.SwitchStateChangeEvent;
import com.pi4j.component.switches.impl.GpioMomentarySwitchComponent;
import com.pi4j.io.gpio.*;
import com.pi4j.io.serial.Serial;
import com.pi4j.io.serial.SerialDataEvent;
import com.pi4j.io.serial.SerialDataListener;
import com.pi4j.io.serial.SerialFactory;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.SimpleEmail;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.*;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;
/**
* This example code demonstrates how to create an access control
* system using a Raspberry Pi and the Pi4J Java library.
*
* Please see the complete article for parts list, wiring diagrams, etc. at
* http://www.savagehomeautomation.com/pi4j-access-control
*
* ATTENTION : THIS CODE IS PROVIDED A AN EXAMPLE ONLY. THIS SAMPLE
* DOES NOT COVER ALL POTENTIAL ACCESS CONTROL USE CASES
* AND SECURITY RISKS. THIS CODE MAY BE USED AS A STARTING
* POINT TO CREATING YOUR OWN ACCESS CONTROL SYSTEM.
* USE AT YOUR OWN RISK. AUTHORS AND/OR CONTRIBUTORS
* ASSUME NO LIABILITY AND ARE NOT LIABLE FOR ANY LOSSES
* RELATED TO THE USE OF THIS CODE AND/OR PROJECT.
*
* @author Robert Savage
*/
public class AccessControl {
public static final boolean EMAIL_ENABLED = false; // TODO : ENABLED EMAIL TO IFTTT HERE
public static final String EMAIL_FROM = ""; //TODO : YOUR IFTTT EMAIL GOES HERE
public static final String EMAIL_TO = "trigger@ifttt.com";
public static final String EMAIL_SERVER = "aspmx.l.google.com";
public static final int EMAIL_PORT = 25;
public static final String IFTTT_TAG_SECURITY_ALERT = "#security-alert";
public static final String IFTTT_TAG_ACCESS_LOG = "#access-log";
// create GPIO controller
private static final GpioController gpio = GpioFactory.getInstance();
// ******************************************************************
// INITIALIZE SENSORS, SWITCHES, RELAYS, etc.
// ******************************************************************
// create doorbell switch
// (momentary push-button switch; activates when button is pushed)
private static MomentarySwitch doorbellSwitch = new GpioMomentarySwitchComponent(
gpio.provisionDigitalInputPin(RaspiPin.GPIO_01, PinPullResistance.PULL_UP),
PinState.HIGH,
PinState.LOW); // this switch is configured to trigger ON with pin LOW
// create manual override switch
// (momentary push-button switch; activates when button is pushed)
private static MomentarySwitch overrideSwitch = new GpioMomentarySwitchComponent(
gpio.provisionDigitalInputPin(RaspiPin.GPIO_06, PinPullResistance.PULL_UP),
PinState.HIGH,
PinState.LOW); // this switch is configured to trigger ON with pin LOW
// create keypad tamper switch
// (momentary push-button switch; activates when button is pushed)
private static MomentarySwitch tamperSwitch = new GpioMomentarySwitchComponent(
gpio.provisionDigitalInputPin(RaspiPin.GPIO_03, PinPullResistance.PULL_UP),
PinState.LOW,
PinState.HIGH); // this switch is configured to trigger ON with pin LOW
// create door sensor
// (magnetic door sensor; senses door opened/closed events)
private static Sensor doorSensor = new GpioSensorComponent(
gpio.provisionDigitalInputPin(RaspiPin.GPIO_00, PinPullResistance.PULL_UP),
PinState.HIGH,
PinState.LOW); // this sensor is configured as "Closed" when the pin goes LOW
// create unlock sensor
// (activates when keypad request unlock event)
private static Sensor keypadUnlockSensor = new GpioSensorComponent(
gpio.provisionDigitalInputPin(RaspiPin.GPIO_02, PinPullResistance.PULL_UP),
PinState.HIGH,
PinState.LOW); // this sensor is configured as "Open" when the pin goes LOW
// create door lock relay controller
// (when relay is latched, the door solenoid is actuated, thus unlocking the door)
private static Relay lockRelay = new GpioRelayComponent(
gpio.provisionDigitalOutputPin(RaspiPin.GPIO_04, PinState.LOW));
// create door opener request relay controller
// (when relay is latched, a request is sent to the keypad controller to unlock the door)
private static Relay keypadOpenerRelay = new GpioRelayComponent(
gpio.provisionDigitalOutputPin(RaspiPin.GPIO_05, PinState.LOW));
// define components
private static LED securityLed = new GpioLEDComponent(
gpio.provisionDigitalOutputPin(RaspiPin.GPIO_07, PinState.LOW));
// static state members
private static SecurityViolation securityViolation = SecurityViolation.None;
// create an instance of the serial communications class
private static final Serial serial = SerialFactory.createInstance();
private static final String LOADING = "A";
private static final String SYSTEM_READY = "B";
private static final String DOORBELL = "C";
private static final String DOOR_OPENED = "D";
private static final String DOOR_CLOSED = "E";
private static final String APPROVED = "F";
private static final String DENIED = "G";
private static final String SECURITY = "H";
private static final String EMPTY = "Y";
public static void main(String args[]) throws Exception {
// init JANSI
// (using jansi for color console output)
AnsiConsole.systemInstall();
// display welcome message
AnsiConsole.out().println(Ansi.ansi().fg(Ansi.Color.CYAN).a(Ansi.Attribute.INTENSITY_BOLD)
.a("-----------------------------------------------\n")
.a("\n")
.a("Welcome to JavaOne 2013 - [[ CON7968 ]]\n")
.a("\n")
.a(" Let's Get Physical: I/O Programming with\n")
.a(" Java on the Raspberry Pi with Pi4J\n")
.a("\n")
.a("-----------------------------------------------\n")
.a("<--Pi4J--> Security Access Example ... started.\n")
.a("-----------------------------------------------\n").reset());
// ******************************************************************
// INIT & CONFIGURE LED MESSAGE READER
// ******************************************************************
// open the default serial port provided on the GPIO header
// (this is where our LED reader is connected)
serial.open(Serial.DEFAULT_COM_PORT, 2400); // 2400 BAUD, N, 8, 1
// configure the LED message display
// (setup pages of display text)
configureMessage(LOADING, "<FD><CL>LOADING...");
configureMessage(SYSTEM_READY, "<FD>SYSTEM READY");
configureMessage(DOORBELL, "<FD><CQ>DOORBELL<FO>");
configureMessage(DOOR_OPENED, "<FD><CK>OPENED");
configureMessage(DOOR_CLOSED, "<FD><CE>CLOSED");
configureMessage(APPROVED, "<FD><CL>APPROVED");
configureMessage(DENIED, "<FD><CH>DENIED");
configureMessage(SECURITY, "<FD><CC><SE>* SECURITY *<FO>");
configureMessage(EMPTY, "<FD><CC> ");
// create and register the serial data listener
serial.addListener(new SerialDataListener() {
@Override
public void dataReceived(SerialDataEvent event) {
// print out the data received to the console
//System.out.print(event.getData());
}
});
// ******************************************************************
// INIT & START WEB SERVER (using Jetty Web Server <embedded>)
// ******************************************************************
// create new jetty web server
Server server = new Server(80);
// create a resource handler to serve up the static html files
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setDirectoriesListed(true);
resourceHandler.setWelcomeFiles(new String[]{ "index.html" });
resourceHandler.setResourceBase("html");
// create a websocket handler to allow communication from the web pages
WebSocketHandler websocketHandler = new WebSocketHandler()
{
@Override
public void configure(WebSocketServletFactory factory)
{
factory.register(AccessControlWebSocket.class);
}
};
// create a handler collection and include all the handlers
HandlerCollection handlerCollection = new HandlerCollection();
handlerCollection.setHandlers(new Handler[] {websocketHandler, resourceHandler, new DefaultHandler()});
server.setHandler(handlerCollection);
// start the jetty web server
server.start();
// ******************************************************************
// DEFINE COMPONENT LISTENERS & EVENT LOGIC
// ******************************************************************
// create door sensor listener
doorSensor.addListener(new SensorListener() {
@Override
public void onStateChange(SensorStateChangeEvent event) {
// display console message
Ansi message = Ansi.ansi().fg(Ansi.Color.WHITE).a("Door sensor event: ");
if (event.getNewState() == SensorState.OPEN) {
message.fg(Ansi.Color.GREEN).a("--DOOR OPENED--");
// check for a security violation
// (this can occur if the lock solenoid is not currently engaged;
// this may mean that the door was forcefully opened)
if (lockRelay.isOpen()) {
// set security violation to 'door-breach'
setSecurityViolation(SecurityViolation.DoorBreach);
}
else{
displayMessage(DOOR_OPENED);
}
} else {
message.fg(Ansi.Color.YELLOW).a("--DOOR CLOSED--");
displayMessage(DOOR_CLOSED);
}
AnsiConsole.out().println(message.reset());
}
});
// create doorbell switch listener
doorbellSwitch.addListener(new SwitchListener() {
public void onStateChange(SwitchStateChangeEvent event) {
if (event.getNewState() == SwitchState.ON) {
// display console message
AnsiConsole.out().println(Ansi.ansi().fgBright(Ansi.Color.CYAN)
.a("DOORBELL event triggered").reset());
displayMessage(DOORBELL);
}
}
});
// create tamper switch listener
tamperSwitch.addListener(new SwitchListener() {
public void onStateChange(SwitchStateChangeEvent event) {
// set security violation to 'tamper'
setSecurityViolation(SecurityViolation.Tamper);
}
});
// create a door lock solenoid relay listener to monitor lock/unlock events
lockRelay.addListener(new RelayListener() {
@Override
public void onStateChange(RelayStateChangeEvent event) {
// display console message
Ansi message = Ansi.ansi().fg(Ansi.Color.WHITE).a("Door locking solenoid: ");
if (event.getNewState() == RelayState.CLOSED) {
message.fg(Ansi.Color.GREEN).a("--UNLOCKED--");
} else {
message.fg(Ansi.Color.RED).a("--LOCKED--");
}
AnsiConsole.out().println(message.reset());
}
});
// create keypad unlock sensor listener
keypadUnlockSensor.addListener(new SensorListener() {
@Override
public void onStateChange(SensorStateChangeEvent event) {
if (event.getNewState() == SensorState.CLOSED) {
// first check for any security violation;
// if a security violation exists, then we will not unlock the
// door until the security violation has been reset/restored
if (securityViolation != SecurityViolation.None) {
// display console message
AnsiConsole.out().println(Ansi.ansi().fg(Ansi.Color.WHITE)
.a("Unlock request received: ")
.fgBright(Ansi.Color.RED)
.a("--ACCESS DENIED--").reset());
// display access denied message
displayMessage(DENIED);
AnsiConsole.out().println(Ansi.ansi().fgBright(Ansi.Color.YELLOW)
.a("A security violation has been detected; the door will ")
.a("not be unlocked until the violation is reset.")
.reset());
// send notification email to update access log
sendEmail(IFTTT_TAG_ACCESS_LOG, "ACCESS DENIED");
}
// if no security violation exists, then its safe to unlock the door
else {
// display console message
AnsiConsole.out().println(Ansi.ansi().fg(Ansi.Color.WHITE)
.a("Unlock request received: ")
.fgBright(Ansi.Color.GREEN)
.a("--ACCESS GRANTED--").reset());
// display access approved message
displayMessage(APPROVED);
// determine if the door is open or shut
// (if the door is already open, then there is
// no need to engage the lock solenoid relay)
if(doorSensor.isOpen()){
// display console message
AnsiConsole.out().println(Ansi.ansi().fg(Ansi.Color.YELLOW)
.a("Unlock bypassed; the door is already open.").reset());
}
else{
// unlock the physical door by latching the
// solenoid relay for a few seconds
lockRelay.pulse(3000);
}
// send notification email to update access log
sendEmail(IFTTT_TAG_ACCESS_LOG, "ACCESS GRANTED");
}
}
}
});
// create override switch listener
overrideSwitch.addListener(new SwitchListener() {
public void onStateChange(SwitchStateChangeEvent event) {
if (event.getNewState() == SwitchState.ON) {
// check for security violation
if (securityViolation != SecurityViolation.None) {
// display console message
AnsiConsole.out().println(Ansi.ansi().fgBright(Ansi.Color.WHITE)
.a("Override switch requesting --RESET--").reset());
// check for tamper switch security violations
if (tamperSwitch.isOn()) {
// display console message
AnsiConsole.out().println(Ansi.ansi().fgBright(Ansi.Color.YELLOW)
.a("Unable to RESET security violation; ")
.a("TAMPER switch is still reporting trouble.\n")
.a("Tamper trouble must be resolved to reset.").reset());
return;
}
// check for drop open security violations
if (doorSensor.isOpen()) {
// display console message
AnsiConsole.out().println(Ansi.ansi().fgBright(Ansi.Color.YELLOW)
.a("Unable to RESET security violation; DOOR is still open.\n")
.a("Door must be closed to reset.").reset());
return;
}
// reset security violation
setSecurityViolation(SecurityViolation.Reset);
}
else {
AnsiConsole.out().println(Ansi.ansi().fg(Ansi.Color.WHITE)
.a("Override switch requesting --UNLOCK--").reset());
unlock();
}
}
}
});
// ******************************************************************
// PROGRAM INIT LOGIC
// ******************************************************************
// display console message
AnsiConsole.out().println(Ansi.ansi().fg(Ansi.Color.WHITE)
.a("SYSTEM READY").reset());
// post read message to LED reader sign
serial.write("<ID01><RPB>\r\n");
// check for tamper switch security violations
if(tamperSwitch.isOn()){
// set security violation to 'tamper'
setSecurityViolation(SecurityViolation.Tamper);
}
// ******************************************************************
// PROGRAM TERMINATION
// ******************************************************************
// wait for user input to terminate program
AnsiConsole.out().println(Ansi.ansi()
.fg(Ansi.Color.BLACK)
.bg(Ansi.Color.CYAN)
.a("PRESS [ENTER] TO EXIT")
.reset());
System.console().readLine();
// make sure the security LED is off
securityLed.blink(0);
securityLed.off();
// shutdown jetty web server
server.stop();
// shutdown GPIO controller
gpio.shutdown();
// clear the display
displayMessage(EMPTY);
Thread.sleep(1000);
// shutdown the serial controller
serial.shutdown();
}
/**
* Get the current security violation state
*
* @return SecurityViolation
*/
public static SecurityViolation getSecurityViolation(){
return securityViolation;
}
/**
* Request to unlock door.
*
* @return 'true' if the door was successfully unlocked; otherwise 'false'
*/
public static boolean unlock(){
// check for security violation
if (securityViolation == SecurityViolation.None) {
AnsiConsole.out().println(Ansi.ansi().fg(Ansi.Color.WHITE)
.a("Requesting --UNLOCK--").reset());
keypadOpenerRelay.pulse(500);
return true;
}
return false;
}
/**
* Display a pre-configured message (page) on the LED message reader board.
*
* @param messageId The page identifier where the message is stored.
*/
private static void displayMessage(String messageId) {
if(securityViolation == SecurityViolation.None ||
messageId.equals(SECURITY)){
serial.write("<ID01><RP" + messageId + ">\r\n");
}
}
/**
* Configure a display message for the LED message reader board.
*
* @param messageId The page identifier where the message is stored.
* @param message The message text to be stored at the givin page location.
* @throws InterruptedException
*/
private static void configureMessage(String messageId, String message) throws InterruptedException {
serial.write("<ID01><P" + messageId + ">" + message + "\r\n");
Thread.sleep(250);
}
/**
* Update the current system security violation status
*
* @param violation New security violation status
*/
private static void setSecurityViolation(SecurityViolation violation){
// ignore if same violation
if(violation == securityViolation){
return;
}
// determine if this is a violation RESET
if(violation == SecurityViolation.Reset){
// reset security violation tracking variable
securityViolation = SecurityViolation.None;
// display console message
AnsiConsole.out().println(Ansi.ansi().fgBright(Ansi.Color.WHITE)
.a("The security violation has been ")
.fgBright(Ansi.Color.MAGENTA)
.a("--RESET--\n").reset()
.fg(Ansi.Color.WHITE)
.a("SYSTEM READY").reset());
// stop security blinking LED
securityLed.blink(0);
securityLed.off();
// send email notification to IFTTT
sendEmail(IFTTT_TAG_SECURITY_ALERT,
"A security violation has been restored: " + violation.getName());
// remove security message from LED message reader
displayMessage(SYSTEM_READY);
}
else{
// set security violation tracking variable
securityViolation = violation;
// display console message
AnsiConsole.out().println(Ansi.ansi().fgBright(Ansi.Color.YELLOW)
.a("A security violation has been detected: ")
.fgBright(Ansi.Color.RED)
.a("--" + violation.getName() + "--").reset());
AnsiConsole.out().println(Ansi.ansi().fgBright(Ansi.Color.RED)
.a("-----------------------------------------------\n")
.a("SECURITY VIOLATION :: " + violation.getName() + "\n")
.a("-----------------------------------------------").reset());
// start blinking the security LED indicator light
securityLed.blink(250);
// send email notification to IFTTT
sendEmail(IFTTT_TAG_SECURITY_ALERT,
"A security violation has been detected: " + violation.getName());
// display the security violation on the LED message reader
displayMessage(SECURITY);
}
}
/**
* Send email message for external notifications
*
* @param subject Email Subject Text
* @param message Email Message Text
*/
private static void sendEmail(String subject, String message){
try{
if(EMAIL_ENABLED && EMAIL_FROM != null && !EMAIL_FROM.isEmpty()){
Email email = new SimpleEmail();
email.setHostName(EMAIL_SERVER);
email.setSmtpPort(EMAIL_PORT);
email.setFrom(EMAIL_FROM);
email.addTo(EMAIL_TO);
email.setSubject(subject);
email.setMsg(message);
email.send();
}
}
catch(Exception ex){
ex.printStackTrace();
}
}
}